package com.meizu.asum.richtextsdk.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;

import com.meizu.asum.richtextsdk.R;
import com.meizu.asum.richtextsdk.config.RichTextAlignment;
import com.meizu.asum.richtextsdk.span.EmoticonSpan;
import com.meizu.asum.richtextsdk.span.PicSpan;
import com.meizu.asum.richtextsdk.table.BaseEmoticonIconTable;
import com.meizu.asum.richtextsdk.tools.ComputeScaleTools;
import com.meizu.asum.richtextsdk.tools.DownloadTools;
import com.meizu.asum.richtextsdk.tools.ScreenSizeTools;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Description： 富文本输入框EditText<br>
 * User：Xie JunWen <br>
 * Email：v-xiejunwen@meizu.com <br>
 * CreateTime：2018/1/9  11:12 <br>
 * Remark：无
 */
public class RichEditText extends android.support.v7.widget.AppCompatEditText {
    public BaseEmoticonIconTable emoticonIconTable;
    private RichTextAlignment textAlignment;
    private int emoticonIconSize;
    private int maxWidth, maxHeight;
    private String picStartWrap, picEndWrap;
    private String emoticonPattern;
    private int maxLength;
    private String[] symbols;
    private ArrayList<String> topics;
    private int picLoadingResId;
    private int picLoadFailResId;

    private Map<String, String> picsMap;
    private Map<Integer, Drawable> resourcesDrawableMap;
    private Map<String, Drawable> fileDrawableMap;

    private DownloadTools downloadTools;
    private RichEditTextCallBack callBack;

    public RichEditText(Context context) {
        super(context);
        init();
    }

    public RichEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        textAlignment = RichTextAlignment.VERTICAL_CENTER;
        maxLength = Integer.MAX_VALUE;
        symbols = new String[]{};
        emoticonIconSize = (int) (ScreenSizeTools.getH(getContext()) * 0.04);
        maxWidth = ScreenSizeTools.getW(getContext()) / 3;
        maxHeight = ScreenSizeTools.getH(getContext()) / 3;
        downloadTools = new DownloadTools(getContext());
        picsMap = new HashMap<>();
        resourcesDrawableMap = new HashMap<>();
        fileDrawableMap = new HashMap<>();
        emoticonPattern = "";
        topics = new ArrayList<>();
        picLoadingResId = R.drawable.img_loading_default;
        picLoadFailResId = R.drawable.img_fail_default;
    }

    /**
     * 设置话题集合
     *
     * @param topics
     */
    public void setTopics(ArrayList<String> topics) {
        this.topics = topics;
    }

    /**
     * 设置话题集合
     *
     * @param topics
     */
    public void setTopics(String... topics) {
        if (this.topics == null) {
            this.topics = new ArrayList<>();
        }

        for (String topic : topics) {
            this.topics.add(topic);
        }
    }

    /**
     * 添加话题
     *
     * @param topic
     */
    public void addTopic(String topic) {
        if (topics == null) {
            topics = new ArrayList<>();
        }
        topics.add(topic);
    }

    /**
     * 设置图片加载时显示的图片资源Id
     *
     * @param picLoadingResId
     */
    public void setPicLoadingResId(int picLoadingResId) {
        this.picLoadingResId = picLoadingResId;
    }

    /**
     * 设置图片加载失败时显示的图片资源Id
     *
     * @param picLoadFailResId
     */
    public void setPicLoadFailResId(int picLoadFailResId) {
        this.picLoadFailResId = picLoadFailResId;
    }

    /**
     * 设置表情表
     *
     * @param emoticonIconTableClass
     * @param isReload               是否重新加载该表，由于使用静态变量保存，对于二次加载时可选为不重新加载
     */
    public void setEmoticonIconTable(Class<?> emoticonIconTableClass, boolean isReload) {
        try {
            emoticonIconTable = (BaseEmoticonIconTable) emoticonIconTableClass.newInstance();
            emoticonIconTable.initialize(isReload);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取表情表
     *
     * @return
     */
    public BaseEmoticonIconTable getEmoticonIconTable() {
        return emoticonIconTable;
    }

    /**
     * 设置富文本元素对齐方式
     *
     * @param textAlignment
     */
    public void setRichTextAlignment(RichTextAlignment textAlignment) {
        this.textAlignment = textAlignment;
    }

    /**
     * 获取富文本元素对齐方式
     *
     * @return
     */
    public RichTextAlignment getRichTextAlignment() {
        return textAlignment;
    }

    /**
     * 设置表情大小，注：正方形显示
     *
     * @param emoticonIconSize 单位为px
     */
    public void setEmoticonIconSize(int emoticonIconSize) {
        this.emoticonIconSize = emoticonIconSize;
    }

    /**
     * 获取表情大小
     *
     * @return
     */
    public int getEmoticonIconSize() {
        return emoticonIconSize;
    }

    /**
     * 设置图片最大的宽度和最大的高度
     *
     * @param maxWidth
     * @param maxHeight
     */
    public void setPicMaxSize(int maxWidth, int maxHeight) {
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
    }

    /**
     * 设置图片的左右包围，例如设置为"<"和">"，则当插入图片时，文字显示为"< www.pic.com/kkk.jpg>"
     *
     * @param picStartWrap
     * @param picEndWrap
     */
    public void setPicWrapPage(String picStartWrap, String picEndWrap) {
        this.picStartWrap = picStartWrap;
        this.picEndWrap = picEndWrap;
    }

    /**
     * 设置识别表情的正则表达式
     *
     * @param emoticonPattern
     */
    public void setEmoticonPattern(String emoticonPattern) {
        this.emoticonPattern = emoticonPattern;
    }

    /**
     * 设置识别表情的正则表达式，此方法为自动生成正则，需提供表情的左右包围，如表情为"[笑脸]"，则传入的包围为"["和"]"
     *
     * @param start
     * @param end
     */
    public void setEmoticonPattern(String start, String end) {
        emoticonPattern = start + "\\S+?" + end;
    }

    /**
     * 获取图片的左包围
     *
     * @return
     */
    public String getPicStartWrap() {
        return picStartWrap;
    }

    /**
     * 获取图片的右包围
     *
     * @return
     */
    public String getPicEndWrap() {
        return picEndWrap;
    }

    /**
     * 设置文字最长值
     *
     * @param maxLength
     */
    public void setTextMaxLength(int maxLength) {
        this.maxLength = maxLength;
    }

    /**
     * 设置触发插入话题回调的字符集
     *
     * @param symbols
     */
    public void setCallBackSymbols(String... symbols) {
        this.symbols = symbols;
    }

    /**
     * 插入表情
     *
     * @param emoticonName
     */
    public void insertEmoticon(String emoticonName) {
        if (emoticonName == null) {
            return;
        }

        int start = getInputStart();
        int end = getInputEnd();

        if (isTooLong(emoticonName)) {
            return;
        }

        replace(start, end, emoticonName);
    }

    /**
     * 插入图片
     *
     * @param picPath
     */
    public void insertPic(String picPath) {
        if (picPath == null || picStartWrap == null || picEndWrap == null) {
            return;
        }

        int start = getInputStart();
        int end = getInputEnd();

        if (isTooLong(picPath)) {
            return;
        }

        replace(start, end, picStartWrap + picPath + picEndWrap);
    }

    /**
     * 插入话题
     *
     * @param topic
     * @param fromSelect 是否为完全插入，当是因输入某个符号触发的插入，为true
     */
    public void insertTopic(String topic, boolean fromSelect) {
        if (topic == null || symbols == null) {
            return;
        }

        int start = getInputStart();
        int end = getInputEnd();

        if (isTooLong(topic)) {
            return;
        }

        if (fromSelect) {
            String symbol = this.getText().subSequence(start - 1, start).toString();
            topic = symbol + topic + symbol;
            this.getText().delete(start - 1, start);
            start = start - 1;
            end = end - 1;
        }

        replace(start, end, topic);
    }

    /**
     * 设置富文本
     *
     * @param richText
     */
    public void setRichText(String richText) {
        if (richText == null) {
            return;
        }

        int start = getInputStart();
        int end = getInputEnd();

        if (isTooLong(richText)) {
            return;
        }

        replace(start, end, richText);
    }

    /**
     * 删除输入
     */
    public void deleteInput() {
        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
        this.onKeyDown(KeyEvent.KEYCODE_DEL, event);
    }

    /**
     * 释放
     */
    public void clear() {
        downloadTools.clear();
    }

    private int getInputStart() {
        int start = this.getSelectionStart() > this.getSelectionEnd() ? this.getSelectionEnd() : this.getSelectionStart();
        return start;
    }

    private int getInputEnd() {
        int end = this.getSelectionStart() > this.getSelectionEnd() ? this.getSelectionStart() : this.getSelectionEnd();

        if (end > this.getText().toString().length()) {
            end = this.getText().toString().length();
        }

        return end;
    }

    private boolean isTooLong(String topic) {
        if (this.getText().length() + topic.length() > maxLength) {
            if (callBack != null) {
                callBack.tooLong(topic);
            }
            return true;
        }
        return false;
    }

    private void replace(int insertStart, int insertEnd, String insertString) {
        StringBuilder sb = new StringBuilder(this.getText().toString());
        sb.replace(insertStart, insertEnd, insertString);
        SpannableString spannableString = new SpannableString(sb);

        String picPattern = picStartWrap + "\\S+?" + picEndWrap;
        String topicPattern = "";
        for (int i = 0; i < symbols.length; i++) {
            topicPattern += "(\\" + symbols[i] + "\\S+?\\" + symbols[i] + ")" + ((i == symbols.length - 1) ? "" : "|");
        }

        Pattern pattern = Pattern.compile("(" + emoticonPattern + ")|(" + picPattern + ")|" + topicPattern);
        Matcher matcher = pattern.matcher(spannableString);
        while (matcher.find()) {
            String findString = matcher.group();
            int start = matcher.start();
            int end = matcher.end();
            int emojiResId = emoticonIconTable.getEmotionResIdFromName(findString);

            if (emojiResId != -1) {
                replaceEmoticon(emojiResId, start, end, spannableString);
            } else if (isPicString(findString)) {
                replacePic(findString, start, end, insertStart, insertEnd, spannableString);
            } else {
                replaceTopic(findString, start, end, spannableString);
            }
        }

        this.setText(spannableString);
        this.setSelection(insertStart + insertString.length());
    }

    private void replaceEmoticon(int emojiResId, int start, int end, SpannableString spannableString) {
        Drawable drawable = resourcesDrawableMap.get(emojiResId);
        if (drawable == null) {
            drawable = getResources().getDrawable(emojiResId);
            drawable.setBounds(0, 0, emoticonIconSize, emoticonIconSize);
            resourcesDrawableMap.put(emojiResId, drawable);
        }

        EmoticonSpan emoticonSpan = new EmoticonSpan(drawable, textAlignment);
        spannableString.setSpan(emoticonSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    private void replacePic(String findString, int start, int end, int insertStart, int insertEnd, SpannableString spannableString) {
        String localUrlString = picsMap.get(findString);
        Drawable drawable;
        if (localUrlString != null) {
            drawable = getPicDrawable(localUrlString);
        } else {
            boolean isUrl = isUrl(findString);
            String existingPath = getExistingPath(findString);

            if (!isUrl) {
                drawable = getPicDrawable(existingPath);
            } else {
                drawable = picFromNet(insertStart, insertEnd, spannableString, findString);
            }
        }
        PicSpan picSpan = new PicSpan(drawable, textAlignment);
        spannableString.setSpan(picSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    private void replaceTopic(String findString, int start, int end, SpannableString spannableString) {
        if (findString != null && findString.length() > 2) {
            for (int i = 0; i < topics.size(); i++) {
                if (topics.get(i).equals(findString.substring(1, findString.length() - 1))) {
                    ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
                    spannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                    break;
                }
            }
        }
    }

    private boolean isPicString(String findString) {
        if (findString != null && findString.length() >= picStartWrap.length() + picEndWrap.length()) {
            if (findString.substring(0, picStartWrap.length()).equals(picStartWrap)) {
                if (findString.substring(findString.length() - picEndWrap.length(), findString.length()).equals(picEndWrap)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isUrl(String picUrlString) {
        Pattern pattern = Pattern.compile("((https|http|ftp|rtsp|mms)?:\\/\\/)[^\\s]+");
        Matcher matcher = pattern.matcher(picUrlString);
        if (matcher.find()) {
            return true;
        }
        return false;
    }

    private String getExistingPath(String picUrlString) {
        picUrlString = picUrlString.length() > 2 ? picUrlString.substring(1, picUrlString.length() - 1) : "";
        if (new File(picUrlString).exists()) {
            return picUrlString;
        }
        return null;
    }

    private Drawable picFromNet(int insertStart, int insertEnd, SpannableString spannableString, final String picUrl) {
        downloadTools.download(insertStart, insertEnd, spannableString, picUrl.substring(picStartWrap.length(), picUrl.length() - picEndWrap.length()), new DownloadTools.DownloadToolsCallBack() {
            @Override
            public void begin() {
            }

            @Override
            public void success(int insertStart, int insertEnd, SpannableString spannableString, String filePath) {
                if (insertStart != 0) {
                    picsMap.put(picUrl, filePath);
                } else {
                    picsMap.put(picUrl, "fail");
                }
                replace(RichEditText.this.getSelectionStart(), RichEditText.this.getSelectionEnd(), "");
            }

            @Override
            public void fail() {
                picsMap.put(picUrl, "fail");
                replace(RichEditText.this.getSelectionStart(), RichEditText.this.getSelectionEnd(), "");
            }
        });

        Drawable drawable = resourcesDrawableMap.get(picLoadingResId);
        if (drawable == null) {
            drawable = getResources().getDrawable(picLoadingResId);
            drawable.setBounds(0, 0, 200, 200);
            resourcesDrawableMap.put(picLoadingResId, drawable);
        }
        return drawable;
    }

    private Drawable getPicDrawable(String localUrlString) {
        Drawable drawable = fileDrawableMap.get(localUrlString);

        if (localUrlString == null || !new File(localUrlString).exists()) {
            drawable = resourcesDrawableMap.get(picLoadFailResId);
            if (drawable == null) {
                drawable = getResources().getDrawable(picLoadFailResId);
                drawable.setBounds(0, 0, 200, 200);
                resourcesDrawableMap.put(picLoadingResId, drawable);
            }
        }

        if (drawable == null) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(localUrlString, options);

            options.inJustDecodeBounds = false;
            options.inSampleSize = ComputeScaleTools.computeScale(options, maxWidth, maxHeight);

            Bitmap bitmap = BitmapFactory.decodeFile(localUrlString, options);
            drawable = new BitmapDrawable(bitmap);
            drawable.setBounds(0, 0, options.outWidth, options.outHeight);

            fileDrawableMap.put(localUrlString, drawable);
        }

        return drawable;
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        if (callBack != null) {
            String input = text.subSequence(start, start + lengthAfter).toString();
            if (symbols != null) {
                for (int i = 0; i < symbols.length; i++) {
                    if (symbols[i].equals(input)) {
                        callBack.inputSymbol(input);
                        break;
                    }
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            InputMethodManager imm = (InputMethodManager) this.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.showSoftInput(this, 0);
        }
        return super.onTouchEvent(event);
    }

    public void setCallBack(RichEditTextCallBack callBack) {
        this.callBack = callBack;
    }

    public interface RichEditTextCallBack {
        void inputSymbol(String symbol);

        void tooLong(String theInsertString);
    }
}